package com.bullsora.kindlerss.business;


import com.bullsora.kindlerss.business.mail.KindleMailSender;
import com.bullsora.kindlerss.model.*;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

public class SubscriptionsServiceImpl implements SubscriptionsService {

  public static final Log LOG = LogFactory.getLog(SubscriptionsService.class);
  public static final int FEED_UPDATE_THRESHOLD = 15;
  public static final String FILE_SUFFIX_PATTERN = "EE_HH_mm_dd_MMM";
  public static final String SEND_SUBSCRIPTION_PATTERN = "/subscriptions/%1$s/sendNewEntries";

  private StructuredHtmlGenerator htmlGenerator;

  @Autowired
  private FeedService feedService;

  @Autowired
  private KindleMailSender mailSender;

  @Autowired
  private SubscriptionsTasksRegistry subscriptionsTasksRegistry;

  public void saveSubscription(Subscription subscription) {
    subscription.persist();
    subscriptionsTasksRegistry.scheduleSubscriptionTask(subscription);
  }

  public Subscription updateSubscription(Subscription subscription) {
    Subscription updatedSubscription = subscription.merge();
    subscriptionsTasksRegistry.scheduleSubscriptionTask(subscription);
    return updatedSubscription;
  }


  @SuppressWarnings("unchecked")
  private void updateFeed(FeedUrl feedUrl) {

    String url = feedUrl.getUrl();

    if (!shouldUpdate(feedUrl)) {
      LOG.info(String.format("The feed url %1$s was updated at %2$s. Skipping update", url, feedUrl.getLastUpdated()));
      return;
    }

    LOG.info("Checking new entries for feed " + url);
    try {
      SyndFeed syndFeed = new SyndFeedInput().build(new XmlReader(new URL(url)));
      for (SyndEntry receivedEntry : (List<SyndEntry>) syndFeed.getEntries()) {
        if (!FeedEntry.existsEntryForFeedUrlWithLink(feedUrl, receivedEntry.getLink())) {
          addFeedEntry(feedUrl, receivedEntry);
        }
      }
      feedUrl.setLastUpdated(new Date());
      feedUrl.persist();
    } catch (FeedException e) {
      LOG.error("Error while reading feed " + feedUrl, e);
    } catch (IOException e) {
      LOG.error("Error while reading feed " + feedUrl, e);
    }
  }

  private boolean shouldUpdate(FeedUrl feedUrl) {
    if (feedUrl.getLastUpdated() == null) {
      return true;
    }
    return System.currentTimeMillis() - feedUrl.getLastUpdated().getTime() > FEED_UPDATE_THRESHOLD * 60 * 1000;
  }

  private void addFeedEntry(FeedUrl feedUrl, SyndEntry receivedEntry) {
    FeedEntry newFeedEntry = new FeedEntry();
    newFeedEntry.setFeedUrl(feedUrl);
    newFeedEntry.setLink(receivedEntry.getLink());
    newFeedEntry.setTitle(receivedEntry.getTitle());
    newFeedEntry.setAuthor(receivedEntry.getAuthor());
    if (receivedEntry.getDescription() != null) {
      newFeedEntry.setDescription(receivedEntry.getDescription().getValue());
    } else if (receivedEntry.getContents() != null && receivedEntry.getContents().size() > 0) {
      newFeedEntry.setDescription(((SyndContent) receivedEntry.getContents().get(0)).getValue());
    }

    newFeedEntry.setPublishedDate(receivedEntry.getPublishedDate());
    newFeedEntry.setReceiveDate(new Date());
    LOG.info("Adding new entry " + newFeedEntry);
    newFeedEntry.persist();
  }

  @Override
  public void sendNewEntries(Long subscriptionId) {
    Subscription subscription = findSubscription(subscriptionId);
    if (subscription == null) {
      LOG.warn("Could not find subscription with id " + subscriptionId);
      return;
    }

    sendNewEntries(subscription);
//    scheduleNextSend(subscription);
    subscription.persist();
  }

/*
  private void scheduleNextSend(Subscription subscription) {
    if (!subscription.getEnabled()) {
      LOG.info("Skipping scheduling subscription " + subscription.getPrefix() + " as it is not enabled");
      return;
    }

    AppUser user = subscription.getAppUser();
    String cronExpr = subscription.getSendCronExpression();
    try {

      CronExpression cronExpression = (CronExpression) memcacheService.get(cronExpr);
      if (cronExpression == null) {
        cronExpression = new CronExpression(cronExpr);
        memcacheService.put(cronExpr, cronExpression, Expiration.byDeltaSeconds(7 * 24 * 3600)); // cache it for 7 days.
      }

      cronExpression.setTimeZone(TimeZone.getTimeZone(user.getTimeZone()));
      Date nextScheduledTime = cronExpression.getNextValidTimeAfter(new Date());

      String url = String.format(SEND_SUBSCRIPTION_PATTERN, subscription.getId());
      if (scheduleTasks) {
        Queue defaultQueue = QueueFactory.getDefaultQueue();
        defaultQueue.add(withUrl(url).method(TaskOptions.Method.GET).etaMillis(nextScheduledTime.getTime()));
      }
      LOG.info("Scheduled next send of url " + url + " at " + nextScheduledTime);
      subscription.setNextCheck(nextScheduledTime);
    } catch (ParseException e) {
      LOG.error("Error while parsing cron expression " + cronExpr + " for subscription " +
          subscription.getPrefix() + ". This subscription won't be checked.");
    }
  }
*/

  private void sendNewEntries(Subscription subscription) {
    LOG.info("Sending new entries for subscription " + subscription.getPrefix());

    updateFeeds(subscription);
    computeNewEntries(subscription);
    if (!subscription.getNonEmptyFeeds().isEmpty()) {
      String html = htmlGenerator.generateHtmlForSubscription(subscription);
      AppUser user = subscription.getAppUser();

      DateFormat format = new SimpleDateFormat(FILE_SUFFIX_PATTERN);
      format.setTimeZone(TimeZone.getTimeZone(user.getTimezone()));
      String fileName = String.format("%1$s_%2$s.html", subscription.getPrefix(), format.format(new Date()));

      mailSender.sendMessage(html, fileName, user.getKindleAccount());
    } else {
      LOG.info("No new entries to send found for subscription " + subscription.getPrefix());
    }

    subscription.setLastChecked(new Date());
  }

  private void computeNewEntries(Subscription subscription) {
    List<Feed> nonEmptyFeeds = new ArrayList<Feed>();
    for (Feed feed : subscription.getFeeds()) {

      List<FeedEntry> feedNewEntries = feedService.findNewEntriesForUrl(subscription.getLastChecked(), feed.getUrl());
      if (feedNewEntries != null && !feedNewEntries.isEmpty()) {
        feed.setNewEntries(new HashSet<FeedEntry>(feedNewEntries));
        nonEmptyFeeds.add(feed);
      }
    }
    subscription.setNonEmptyFeeds(nonEmptyFeeds);
  }

  private void updateFeeds(Subscription subscription) {
    for (Feed feed : subscription.getFeeds()) {
      updateFeed(feed.getUrl());
    }
  }

  public StructuredHtmlGenerator getHtmlGenerator() {
    return htmlGenerator;
  }

  public void setHtmlGenerator(StructuredHtmlGenerator htmlGenerator) {
    this.htmlGenerator = htmlGenerator;
  }

  public KindleMailSender getMailSender() {
    return mailSender;
  }

  public void setMailSender(KindleMailSender mailSender) {
    this.mailSender = mailSender;
  }
}
